﻿#region Copyright and License Notice
// Copyright (C)2010-2016 - Rob Levine and other contributors
// http://configgen.codeplex.com
// 
// This file is part of ConfigGen.
// 
// ConfigGen is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// ConfigGen is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License and 
// the GNU Lesser General Public License along with ConfigGen.  
// If not, see <http://www.gnu.org/licenses/>
#endregion

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using ConfigGen.Utilities.Extensions.System;

namespace ConfigGen.ConsoleApp.Core.CommandLineOptionCore
{
    /// <summary>
    /// Factory for providing <see cref="ICommandLineOption{T}"/> instances, based on the handler's <see cref="ICommandLineOption{T}.ShortName"/> or
    /// <see cref="ICommandLineOption{T}.LongName"/> properties.
    /// </summary>
    /// <typeparam name="T">Type of settings object that is set by the <see cref="ICommandLineOption{T}"/> instances.</typeparam>
    public class CommandLineOptionFactory<T> : ICommandLineOptionFactory<T>
    {
        private readonly Dictionary<string, ICommandLineOption<T>> _argsByShortName = new Dictionary<string, ICommandLineOption<T>>(StringComparer.InvariantCulture);
        private readonly Dictionary<string, ICommandLineOption<T>> _argsByLongName = new Dictionary<string, ICommandLineOption<T>>(StringComparer.InvariantCulture);

        /// <summary>
        /// Loads the supplied handler into the factory
        /// </summary>
        /// <param name="argHandler">Arg handler to load</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="argHandler"/> is null.</exception>
        /// <exception cref="FactoryLoadException">Thrown if an entry already exists in the factory that has
        /// either the <see cref="ICommandLineOption{T}.ShortName"/> or the <see cref="ICommandLineOption{T}.LongName"/> 
        /// of the supplied arg handler, or if the supplied arg handler has both its short and long names as null or empty.</exception>
        protected void LoadConsoleArgHandler(ICommandLineOption<T> argHandler)
        {
            if (argHandler == null) throw new ArgumentNullException("argHandler");

            if (argHandler.ShortName.IsNullOrEmpty()
                && argHandler.LongName.IsNullOrEmpty())
            {
                var msg = string.Format(CultureInfo.InvariantCulture,
                                            "Cannot load console arg handler \"{0}\" as its has neither a long name, nor a short name.",
                                            argHandler.GetType().Name);
                throw new FactoryLoadException(msg);
            }

            if (!argHandler.ShortName.IsNullOrEmpty())
            {
                if (_argsByShortName.ContainsKey(argHandler.ShortName))
                {
                    var msg = string.Format(CultureInfo.InvariantCulture,
                                            "Cannot load console arg handler \"{0}\" as its specified short name, \"{1}\" is already in use",
                                            argHandler.GetType().Name, argHandler.ShortName);
                    throw new FactoryLoadException(msg);
                }
                _argsByShortName.Add(argHandler.ShortName, argHandler);
            }

            if (!argHandler.LongName.IsNullOrEmpty())
            {
                if (_argsByLongName.ContainsKey(argHandler.LongName))
                {
                    var msg = string.Format(CultureInfo.InvariantCulture,
                                            "Cannot load console arg handler \"{0}\" as its specified long name, \"{1}\" is already in use",
                                            argHandler.GetType().Name, argHandler.LongName);
                    throw new FactoryLoadException(msg);
                }

                _argsByLongName.Add(argHandler.LongName, argHandler);
            }
        }

        /// <summary>
        /// Returns a handler with a corresponding <see cref="ICommandLineOption{T}.LongName"/> to the supplied parameter,
        /// or throws a <see cref="UnrecognisedCommandLineOptionException"/> if no match.
        /// </summary>
        /// <param name="longName">Long name of handler</param>
        /// <returns>Handler corresponding to supplied long name</returns>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="longName"/> is null</exception>
        /// <exception cref="ArgumentException">Thrown if <paramref name="longName"/> is zero length</exception>
        /// <exception cref="UnrecognisedCommandLineOptionException">Thrown if no handler matches the supplied name</exception>
        public ICommandLineOption<T> GetByLongName(string longName)
        {
            if (longName == null) throw new ArgumentNullException("longName");
            if (longName.Length == 0) throw new ArgumentException("longName cannot be zero length");
            if (!_argsByLongName.ContainsKey(longName))
            {
                var msg = string.Format(CultureInfo.InvariantCulture,
                                        "No console argument handler found for long name \"{0}\"",
                                        longName);

                throw new UnrecognisedCommandLineOptionException(msg, null, longName);
            }

            return _argsByLongName[longName];
        }

        /// <summary>
        /// Returns a handler with a corresponding <see cref="ICommandLineOption{T}.ShortName"/> to the supplied parameter,
        /// or throws a <see cref="UnrecognisedCommandLineOptionException"/> if no match.
        /// </summary>
        /// <param name="shortName">Short name of handler</param>
        /// <returns>Handler corresponding to supplied short name</returns>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="shortName"/> is null</exception>
        /// <exception cref="ArgumentException">Thrown if <paramref name="shortName"/> is zero length</exception>
        /// <exception cref="UnrecognisedCommandLineOptionException">Thrown if no handler matches the supplied name</exception>
        public ICommandLineOption<T> GetByShortName(string shortName)
        {
            if (shortName == null) throw new ArgumentNullException("shortName");
            if (shortName.Length == 0) throw new ArgumentException("shortName cannot be zero length");
            if (!_argsByShortName.ContainsKey(shortName))
            {
                var msg = string.Format(CultureInfo.InvariantCulture,
                                        "No console argument handler found for short name \"{0}\"",
                                        shortName);

                throw new UnrecognisedCommandLineOptionException(msg, shortName, null);
            }

            return _argsByShortName[shortName];
        }

        /// <summary>
        /// Returns a list of the loaded handlers
        /// </summary>
        public IList<ICommandLineOption<T>> LoadedHandlers
        {
            get
            {
                return _argsByShortName.Values.ToList();
            }
        }
    }
}
